﻿/* mvcForum
 * Copyright (C) 2010-2011 Steen F. Tøttrup
 * http://mvcforum.org/
 */

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

using mvcForum.Core;
using mvcForum.Core.Abstractions.Interfaces;
using mvcForum.Core.Events;
using mvcForum.Core.Specifications;
using mvcForum.Core.Interfaces.Data;
using mvcForum.Core.Interfaces.Events;

namespace mvcForum.Web.Providers {

	public class MembershipProviderWrapper : MembershipProvider {
		private String wrappedProvider;

		public override void Initialize(String name, NameValueCollection config) {
			base.Initialize(name, config);

			if (String.IsNullOrWhiteSpace(config["WrappedProvider"])) {
				throw new ArgumentNullException("WrappedProvider");
			}
			this.wrappedProvider = config["WrappedProvider"];
		}

		protected IContext Context {
			get {
				return DependencyResolver.Current.GetService<IContext>();
			}
		}

		protected IRepository<ForumUser> UserRepository {
			get {
				return DependencyResolver.Current.GetService<IRepository<ForumUser>>();
			}
		}

		protected IRepository<Group> GroupRepository {
			get {
				return DependencyResolver.Current.GetService<IRepository<Group>>();
			}
		}

		protected IRepository<GroupMember> GroupMemberRepository {
			get {
				return DependencyResolver.Current.GetService<IRepository<GroupMember>>();
			}
		}

		private MembershipProvider mp = null;
		public MembershipProvider ActualProvider {
			get {
				if (mp == null) {
					mp = Membership.Providers[wrappedProvider];
					if (mp == null) {
						throw new ArgumentNullException("wrappedProvider");
					}
				}
				return mp;
			}
		}

		public override String ApplicationName {
			get {
				return this.ActualProvider.ApplicationName;
			}
			set {
				this.ActualProvider.ApplicationName = value;
			}
		}

		public override Boolean ChangePassword(String username, String oldPassword, String newPassword) {
			return this.ActualProvider.ChangePassword(username, oldPassword, newPassword);
		}

		public override Boolean ChangePasswordQuestionAndAnswer(String username, String password, String newPasswordQuestion, String newPasswordAnswer) {
			return this.ActualProvider.ChangePasswordQuestionAndAnswer(username, password, newPasswordAnswer, newPasswordAnswer);
		}

		public override MembershipUser CreateUser(String username, String password, String email, String passwordQuestion, String passwordAnswer, Boolean isApproved, Object providerUserKey, out MembershipCreateStatus status) {
			IEventPublisher eventPublisher = DependencyResolver.Current.GetService<IEventPublisher>();
			NewUserEvent e = new NewUserEvent { Username = username, EmailAddress = email, IPAddress = HttpContext.Current.Request.UserHostAddress };
			eventPublisher.Publish(e);
			// If any of the anti-bot event handlers are running synchronously, we better check!
			if (e.Bot) {
				status = MembershipCreateStatus.UserRejected;
				return null;
			}

			MembershipUser user = this.ActualProvider.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status);
			if (status == MembershipCreateStatus.Success) {
				try {
					//IUnitOfWork uow = this.UnitOfWork;

					IConfiguration config = DependencyResolver.Current.GetService<IConfiguration>();
					ForumUser u = new ForumUser(user.ProviderUserKey.ToString(), user.UserName, user.Email, HttpContext.Current.Request.UserHostAddress);
					u.Timezone = config.DefaultTimezone;
					u.Culture = config.DefaultCulture;
					this.UserRepository.Create(u);
					this.Context.SaveChanges();

					IRepository<GroupMember> gmRepo = this.GroupMemberRepository;
					foreach (Int32 groupId in DependencyResolver.Current.GetService<IConfiguration>().NewUserGroups) {
						if (groupId > 0) {
							Group group = this.GroupRepository.Read(groupId);
							gmRepo.Create(new GroupMember(group, u));
						}
					}

					this.Context.SaveChanges();
				}
				catch {
					// TODO: Log this!!
				}
			}
			return user;
		}

		public override Boolean DeleteUser(String username, Boolean deleteAllRelatedData) {
			return this.ActualProvider.DeleteUser(username, deleteAllRelatedData);
			try {
				IEnumerable<ForumUser> users = this.UserRepository.ReadMany(new ForumUserSpecifications.SpecificUsername(username));
				// TODO: One/many ?!?!?!
				foreach (ForumUser user in users) {
					user.Deleted = true;
					if (deleteAllRelatedData) {
						// TODO: Delete follow topic/forum, delete topic/forum track!
					}
				}
				this.Context.SaveChanges();
			}
			catch {
				// TODO: Log this!!
			}
		}

		public override Boolean EnablePasswordReset {
			get {
				return this.ActualProvider.EnablePasswordReset;
			}
		}

		public override Boolean EnablePasswordRetrieval {
			get {
				return this.ActualProvider.EnablePasswordRetrieval;
			}
		}

		public override MembershipUserCollection FindUsersByEmail(String emailToMatch, Int32 pageIndex, Int32 pageSize, out Int32 totalRecords) {
			return this.ActualProvider.FindUsersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords);
		}

		public override MembershipUserCollection FindUsersByName(String usernameToMatch, Int32 pageIndex, Int32 pageSize, out Int32 totalRecords) {
			return this.ActualProvider.FindUsersByName(usernameToMatch, pageIndex, pageSize, out totalRecords);
		}

		public override MembershipUserCollection GetAllUsers(Int32 pageIndex, Int32 pageSize, out Int32 totalRecords) {
			return this.ActualProvider.GetAllUsers(pageIndex, pageSize, out totalRecords);
		}

		public override Int32 GetNumberOfUsersOnline() {
			return this.ActualProvider.GetNumberOfUsersOnline();
		}

		public override String GetPassword(String username, String answer) {
			return this.ActualProvider.GetPassword(username, answer);
		}

		/// <summary>
		/// The method updates the lastvisit datestamp to now!
		/// </summary>
		/// <param name="user">The user in question</param>
		private void UpdateVisit(MembershipUser user) {
			ForumUser u = this.UserRepository.ReadOne(new ForumUserSpecifications.SpecificProviderUserKey(user.ProviderUserKey.ToString()));
			u.LastVisit = DateTime.UtcNow;
			this.Context.SaveChanges();
		}

		public override MembershipUser GetUser(String username, Boolean userIsOnline) {
			MembershipUser user = this.ActualProvider.GetUser(username, userIsOnline);
			// Did we get a user, and is he/she online?
			if (user != null && userIsOnline) {
				// Update lastvisit!
				this.UpdateVisit(user);
			}
			return user;
		}

		public override MembershipUser GetUser(Object providerUserKey, Boolean userIsOnline) {
			MembershipUser user = this.ActualProvider.GetUser(providerUserKey, userIsOnline);
			// Did we get a user, and is he/she online?
			if (user != null && userIsOnline) {
				// Update lastvisit!
				this.UpdateVisit(user);
			}
			return user;
		}

		public override String GetUserNameByEmail(String email) {
			return this.ActualProvider.GetUserNameByEmail(email);
		}

		public override Int32 MaxInvalidPasswordAttempts {
			get {
				return this.ActualProvider.MaxInvalidPasswordAttempts;
			}
		}

		public override Int32 MinRequiredNonAlphanumericCharacters {
			get {
				return this.ActualProvider.MinRequiredNonAlphanumericCharacters;
			}
		}

		public override Int32 MinRequiredPasswordLength {
			get {
				return this.ActualProvider.MinRequiredPasswordLength;
			}
		}

		public override Int32 PasswordAttemptWindow {
			get {
				return this.ActualProvider.PasswordAttemptWindow;
			}
		}

		public override MembershipPasswordFormat PasswordFormat {
			get {
				return this.ActualProvider.PasswordFormat;
			}
		}

		public override String PasswordStrengthRegularExpression {
			get {
				return this.ActualProvider.PasswordStrengthRegularExpression;
			}
		}

		public override Boolean RequiresQuestionAndAnswer {
			get {
				return this.ActualProvider.RequiresQuestionAndAnswer;
			}
		}

		public override Boolean RequiresUniqueEmail {
			get {
				return this.ActualProvider.RequiresUniqueEmail;
			}
		}

		public override String ResetPassword(String username, String answer) {
			return this.ActualProvider.ResetPassword(username, answer);
		}

		public override Boolean UnlockUser(String userName) {
			// TODO:
			return this.ActualProvider.UnlockUser(userName);
		}

		public override void UpdateUser(MembershipUser user) {
			ForumUser u = this.UserRepository.ReadOne(new ForumUserSpecifications.SpecificProviderUserKey(user.ProviderUserKey.ToString()));
			MembershipUser mu = this.GetUser(user.ProviderUserKey, false);
			//if (u.ExternalAccount && mu.UserName != user.UserName) {
			//    // DO NOT CHANGE USERNAME on external user (membershipuser).
			//}
			this.ActualProvider.UpdateUser(user);
			if (!u.ExternalAccount) {
				u.Name = user.UserName;
			}
			u.EmailAddress = user.Email;
			// TODO: ??
			this.Context.SaveChanges();
		}

		public override Boolean ValidateUser(String username, String password) {
			Boolean loggedOn = this.ActualProvider.ValidateUser(username, password);
			if (loggedOn) {
				MembershipUser user = this.GetUser(username, false);
				if (user != null) {
					ForumUser u = this.UserRepository.ReadOne(new ForumUserSpecifications.SpecificProviderUserKey(user.ProviderUserKey.ToString()));
					if (u != null) {
						if (u.Active && !u.Deleted) {
							u.LastIP = HttpContext.Current.Request.UserHostAddress;
							u.LastVisit = DateTime.UtcNow;
							this.Context.SaveChanges();

							return true;
						}
					}
					else {
						throw new ArgumentException("No local user found");
					}
				}
				else {
					throw new ArgumentException("No membership user found");
				}
			}

			return false;
		}
	}
}